/* eslint-disable class-methods-use-this, no-param-reassign */
import randomString from 'randomstring';
import log4js from 'koa-log4';
import rtmOutEvents from '../rtm/outgoing/eventTypes';
import messageTypes from '../rtm/outgoing/messageTypes';
import Group from '../models/group';
import Channel from '../models/channel';
import User from '../models/user';
import ChannelUser from '../models/channelUser';
import messageHandler from './messageHandler';
import waitHandler from './waitHandler';
import { USER_GROUP_STUDENT, USER_GROUP_TEACHER } from '../common/groups';
import componentContainer from '../../components/componentContainer';
import { REDIS_SYSTEM_CHANNEL } from '../common/redisChannels';

// const channelHandlerLogger = log4js.getLogger('CHANNEL_HANDLER');
class ChannelHandler {
  async sendChannelJoinMessage(channel, user) {
    return messageHandler.post(channel.id, user, {
      subType: messageTypes.CHANNEL_JOIN,
      body: `${user.username} 加入了频道`,
      fromUser: user,
    });
  }

  async sendChannelLeaveMessage(channel, user) {
    return messageHandler.post(channel.id, user, {
      subType: messageTypes.CHANNEL_LEAVE,
      body: `${user.username} 离开了频道`,
      fromUser: user,
    });
  }

  async publishRedisChannelJoinedEvent(channel, user) {
    const redis = componentContainer.getRedis();
    const payload = {
      name: channel.name,
      id: channel.id,
      user: user.id,
      group: channel.group && {
        id: channel.group.id,
        name: channel.group.name,
      },
    };

    return redis.publish(
      user.getRedisUserNoticeChannelName(),
      JSON.stringify({ event: rtmOutEvents.CHANNEL_JOINED, payload }),
    );
  }

  async publishRedisChannelLeaveEvent(channel, user) {
    const redis = componentContainer.getRedis();
    const payload = {
      name: channel.name,
      id: channel.id,
      user: user.id,
    };

    return redis.publish(
      user.getRedisUserNoticeChannelName(),
      JSON.stringify({ event: rtmOutEvents.CHANNEL_LEFT, payload }),
    );
  }

  publishRedisChannelCreatedEvent(redisChannels, payload) {
    const redis = componentContainer.getRedis();
    const { isPrivate } = payload;
    const redisTopics = [isPrivate ? null : REDIS_SYSTEM_CHANNEL]
      .concat(redisChannels)
      .filter(Boolean);

    const publishToRedis = t =>
      redis.publish(
        t,
        JSON.stringify({
          event: rtmOutEvents.CHANNEL_CREATED,
          payload: { channel: payload },
        }),
      );

    redisTopics.forEach(publishToRedis);
  }

  async join(channel, user) {
    const channelUser = await channel.getChannelUser(user);

    if (channelUser) return Promise.resolve();

    channel.members.addToSet(user);
    user.channels.addToSet(channel);

    const newChannelUser = new ChannelUser({ user, channel, unreadCount: 1 });
    await Promise.all([channel.save(), user.save(), newChannelUser.save()]);
    await this.sendChannelJoinMessage(channel, user);
    await this.publishRedisChannelJoinedEvent(channel, user);

    const group = await Group.findOne({
      _id: channel.group,
      name: { $in: [USER_GROUP_STUDENT, USER_GROUP_TEACHER] },
    });

    if (group) {
      await waitHandler.remove(channel, group);
    }

    return Promise.resolve();
  }

  async leave(channel, user) {
    const channelUser = await channel.getChannelUser(user);

    if (!channelUser) return Promise.resolve();

    await Promise.all([
      this.sendChannelLeaveMessage(channel, user),
      this.publishRedisChannelLeaveEvent(channel, user),
    ]);

    return Promise.all([
      Channel.update({ _id: channel.id }, { $pull: { members: user.id } }),
      User.update({ _id: user.id }, { $pull: { channels: channel.id } }),
      channelUser.remove(),
    ]);
  }

  async read(channelId, user) {
    return ChannelUser.findOneAndUpdate(
      {
        channel: channelId,
        user,
      },
      {
        lastActiveAt: new Date(),
        unreadCount: 0,
      },
    );
  }

  async findOrCreateCustomerChannel(user) {
    const channel = await Channel.findOne({
      isPrivate: true,
      customer: user,
    }).populate('group');

    if (channel) return channel;

    let newChannelName = user.mobile ? `${user.nickname}:${user.mobile}` : user.nickname;
    const duplicate = await Channel.findOne({ name: newChannelName });

    if (duplicate) {
      newChannelName = `${newChannelName}:${randomString.generate(4)}`;
    }

    const newChannel = new Channel({
      name: newChannelName,
      isPrivate: true,
      customer: user,
      group: user.group,
      creator: user,
      members: [user],
    });
    await newChannel.save();
    user.channels.push(newChannel);
    const channelUser = new ChannelUser({
      user,
      channel: newChannel,
    });

    await Promise.all([user.save(), channelUser.save()]);
    return newChannel;
  }
}

export default new ChannelHandler();
